package com.mebs.edi.scheduler.jersey;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.mebs.edi.datalayer.DaoFactory;
import com.mebs.edi.model.edi.EdiGs;
import com.mebs.edi.model.edi.EdiIsa;
import com.mebs.edi.model.edi.EdiSt;
import com.mebs.edi.model.email.Email;
import com.mebs.edi.parser.MebsToEdiMapper;
import com.mebs.edi.parser.dom.ConvertionException;
import com.mebs.edi.parser.dom.ModelToDomConverter;
import com.mebs.edi.parser.xml.XmlGenerationException;
import com.mebs.edi.parser.xml.XmlGenerator;
import com.mebs.edi.parser.xml.XmlParser;
import com.mebs.edi.parser.xml.XmlValidationException;
import com.mebs.edi.scheduler.InitApplication;
import com.mebs.edi.scheduler.email.EmailSender;
import com.mebs.edi.scheduler.ftp.FtpAccessor;
import com.mebs.edi.scheduler.jobs.ProcessXmlFileException;
import com.mebs.edi.scheduler.jobs.ProcessXmlJob;
import com.mebs.model.logistics.Air;
import com.mebs.model.logistics.Ocean;
import com.mebs.model.logistics.Transportation;

@Path("/generateEdi/{transportationId: [0-9]+}")
public class Generate214WebService {
	
	private static final Logger LOG = Logger.getLogger(Generate214WebService.class);
	
	private static final String PROPERTY_GENERATED_XML_LOCATION = "generateXml.directory";
	
	private static final String PROPERTY_FTP_SERVER = "ftp.server";
	private static final String PROPERTY_FTP_USERNAME = "ftp.username";
	private static final String PROPERTY_FTP_PASSWORD = "ftp.password";
	
	private static final String PROPERTY_FTP_NEW_214_DIR = "ftp.new214XmlDir";
	private static final String PROPERTY_FTP_ALL_214_DIR = "ftp.all214XmlDir";
	private static final String PROPERTY_FTP_ERROR_214_DIR = "ftp.error214XmlDir";
	
	private static final String PROPERTY_SCHEMA_214_LOCATION = "schema.214location";
	
	private static final String PROPERTY_SMTP_HOST = "email.smtp.host";
	private static final String PROPERTY_SMTP_PORT = "email.smtp.port";
	
	private static final String PROPERTY_214_NOTIFICATION_RECIPIENTS = "email.214notificationRecipients";
	private static final String PROPERTY_NOTIFICATION_SENDER = "email.notificationSender";
	
	private static final File generatedXmlDir;
	
	private static final String ftpServer;
	private static final String ftpUsername;
	private static final String ftpPassword;
	
	private static final String ftpNew214Dir;
	private static final String ftpAll214Dir;
	private static final String ftpError214Dir;
	
	private static final File xsd214;
	
	static {
		Properties applicationProps = InitApplication.getApplicationProperties();
		String generatedXmlDirLoc = applicationProps.getProperty(PROPERTY_GENERATED_XML_LOCATION);
		generatedXmlDir = new File(generatedXmlDirLoc);
		
		ftpServer = applicationProps.getProperty(PROPERTY_FTP_SERVER);
		ftpUsername = applicationProps.getProperty(PROPERTY_FTP_USERNAME);
		ftpPassword = applicationProps.getProperty(PROPERTY_FTP_PASSWORD);
		
		ftpNew214Dir = applicationProps.getProperty(PROPERTY_FTP_NEW_214_DIR);
		ftpAll214Dir = applicationProps.getProperty(PROPERTY_FTP_ALL_214_DIR);
		ftpError214Dir = applicationProps.getProperty(PROPERTY_FTP_ERROR_214_DIR);
		
		String xsd214loc = applicationProps.getProperty(PROPERTY_SCHEMA_214_LOCATION);
		xsd214 = new File(xsd214loc);
	}
	
	@POST
	@Produces("text/plain")
	public String generate214Edi(
			@Context SecurityContext sc,
			@PathParam("transportationId") Long transportationId) {
		LOG.info("Processing 214 generation request for Transportation with Id: " + transportationId);
		String resultString;
		
		Transportation t = DaoFactory.getInstance().getTransportationDao().getById(transportationId);
		
		FtpAccessor ftpAccessor = new FtpAccessor(ftpServer, ftpUsername, ftpPassword);
		
		MebsToEdiMapper mapper = new MebsToEdiMapper();
		EdiIsa edi = null;
		try {
			edi = mapper.create214Edi(t);
		} catch (IllegalArgumentException e) {
			LOG.info("Transportation has no updated legs, aborting 214 generation");
			return "214 XML generation stopped: " + e.getMessage();
		}
		
		try {
			// filtering ST to find the ones that weren't processed
			List<EdiSt> previouslyProcessedSt = filterUnprocessedStSections(edi.getGs().get(0));
			
			ModelToDomConverter converter = new ModelToDomConverter();
			Document dom = converter.convertToDocument(edi);
			
			// TODO: perhaps a more appropriate naming strategy can be found
			String outputFileName = "214_" + String.valueOf(System.currentTimeMillis());
			File outputFile = new File(generatedXmlDir, outputFileName + ".xml");
			
			XmlGenerator generator = new XmlGenerator();
			generator.generate(dom, outputFile);
			
			// validating the file before uploading to FTP
			XmlParser parser = new XmlParser(xsd214);
			parser.parse(outputFile);
			
			// restoring previously processed ST sections
			// to prevent nulling 211's references to GS (gs_id column) after saving changes to DB
			edi.getGs().get(0).getStSections().addAll(previouslyProcessedSt);
			
			// saving changes to DB AFTER validating the file
			DaoFactory.getInstance().getGenericDao().update(edi.getGs().get(0));
			
			// saving changes to transportation and legs (last update/generate EDI time)
			DaoFactory.getInstance().getGenericDao().update(t);
				
			// uploading the file, if it was parsed successfully (which means it is valid)
			ftpAccessor.uploadFile(outputFile, ftpNew214Dir);
			ftpAccessor.uploadFile(outputFile, ftpAll214Dir);
		} catch (Exception e) {
			resultString = "Generating EDI XML failed: " + e.getMessage();
			LOG.error("Generating EDI XML failed", e);
			
			sendErrorNotificationEmail(e, t);
			
			// if a file processing failed - upload that file to FTP error dir
			if (e instanceof XmlValidationException) {
				XmlValidationException xe = (XmlValidationException)e;
				File f = xe.getXml();
				try {
					ftpAccessor.uploadFile(f, ftpError214Dir);
				} catch (IOException ioe) {
					LOG.fatal("Cannot upload invalid file " + f.getName() + " to FTP " + ftpError214Dir, ioe);
				}
			}
			
			return resultString;
		}
		
		resultString = "214 EDI has been succesfully generated for shipment " + t.getSequenceNumber();
		
		LOG.info("Processing 214 generation request finished: " + resultString);
		return resultString;
	}

	private List<EdiSt> filterUnprocessedStSections(EdiGs ediGs) {
		List<EdiSt> processed = new ArrayList<EdiSt>();
		List<EdiSt> unprocessed = new ArrayList<EdiSt>();
		
		for (EdiSt st : ediGs.getStSections()) {
			if (st.getId() == null || st.getId() == 0) {
				unprocessed.add(st);
			} else {
				processed.add(st);
			}
		}
		
		ediGs.setStSections(unprocessed);
		return processed;
	}

	private void sendErrorNotificationEmail(Exception e, Transportation t) {
		Properties applicationProps = InitApplication.getApplicationProperties();
		
		String sender = applicationProps.getProperty(PROPERTY_NOTIFICATION_SENDER);
		String[] recipients = applicationProps.getProperty(PROPERTY_214_NOTIFICATION_RECIPIENTS).split(",");
		String text = constructErrorMessage(e, t);
		
		Email errorNotification = new Email();
		errorNotification.setSender(sender);
		errorNotification.setRecipients(recipients);
		errorNotification.setSubject("MEBS EDI integration service: generating 214 XML failed");
		errorNotification.setText(text);
		
		String smtpHost = applicationProps.getProperty(PROPERTY_SMTP_HOST);
		String smtpPort = applicationProps.getProperty(PROPERTY_SMTP_PORT);
		
		EmailSender emailSender = new EmailSender(smtpHost, smtpPort);
		try {
			emailSender.sendEmail(errorNotification);
		} catch (MessagingException me) {
			LOG.fatal("Cannot send error notification!", me);
		}
	}
	
	private String constructErrorMessage(Exception e, Transportation t) {
		StringBuilder text = new StringBuilder("214 XML generating job encountered an error:\n\n");
		
		if (e instanceof XmlValidationException) {
			// the file did not pass validation
			XmlValidationException xe = (XmlValidationException)e;
			text.append("validating the file against 214.xsd failed:\n");
			text.append(xe.getXml().getName());
			text.append("\n\n");
			
			List<SAXParseException> validationErrors = xe.getValidationErrors();
			for (SAXParseException error : validationErrors) {
				text.append(error.getMessage());
				text.append("\n");
			}
			
			text.append("\nThe following shipment was used to generate the file:\n");
			text.append((t instanceof Air) ? "Air" : (t instanceof Ocean) ? "Ocean" : "Truck");
			text.append(" ");
			text.append(t.getSequenceNumber());
			text.append("\nCompany: ");
			text.append(t.getCompany().getName());
			if (t.getProject() != null) {
				text.append("\nProject: ");
				text.append(t.getProject().getName());
			}
		} else {
			// something other, perhaps FTP is unavailable
			text.append(e.getClass());
			text.append(": ");
			text.append(e.getMessage());
		}
		
		return text.toString();
	}
}
